package com.magi.builder;

import com.magi.core.AppUtils;

import java.io.IOException;

public class JavaFileBuilder implements IFileBuilder{

    private boolean packageFlag = false;

    private boolean classFlag = false;
    private String className;

    private boolean finishFlag = false;

    public static class FunctionArg {
        public final String type;
        public final String name;

        public FunctionArg(String type, String name) {
            this.type = type;
            this.name = name;
        }
    }

    public enum Qualifiers {
        PUBLIC(Identifiers.PUBLIC),
        PRIVATE(Identifiers.PRIVATE);

        public String getVal() {
            return val;
        }

        private final String val;

        Qualifiers(String val) {
            this.val = val;
        }
    }

    private final StringBuilder builder = new StringBuilder();

    private void addCRLF() {
        this.builder.append(Identifiers.CRLF);
    }

    public void newLine(int count) {
        for (int i=0; i<count; i++) { this.newLine(); }
    }

    public void newLine() {
        this.addCRLF();
    }

    public void addPackage(String packageName) {
        if (packageName == null || packageName.isEmpty()) {
            throw new RuntimeException("package name could not be null");
        }
        if (this.packageFlag) { throw new RuntimeException("multiple package name declare"); }
        this.builder.append(Identifiers.PACKAGE)
                .append(Identifiers.SPACE)
                .append(packageName)
                .append(Identifiers.SEMICOLON);
        this.newLine(2);
        this.packageFlag = true;
    }

    public void importPackage(String name) {
        this.builder.append(Identifiers.IMPORT)
                .append(Identifiers.SPACE)
                .append(name)
                .append(Identifiers.SEMICOLON);
        this.newLine();
    }

    public void addClass(String name) {
        this.addClass(name, null);
    }

    public void addClass(String name, String[] implementNames) {
        if (name == null || name.isEmpty()) {
            throw new RuntimeException("class name could not be null");
        }
        if (this.classFlag) {
            throw new RuntimeException("you already have a public class");
        }
        this.builder.append(Identifiers.PUBLIC)
                .append(Identifiers.SPACE)
                .append(Identifiers.CLASS)
                .append(Identifiers.SPACE)
                .append(name)
                .append(Identifiers.SPACE)
                .append(this.buildImplements(implementNames))
                .append(Identifiers.SPACE)
                .append(Identifiers.BRACE_L);
        this.newLine(2);
        this.classFlag = true;
        this.className = name;
    }

    public void finishClass() {
        this.builder.append(Identifiers.BRACE_R);
    }

    public void addFunction(FunctionMetaData metaData) {
        this.addFunction(
                metaData.getQualifier(),
                metaData.getName(),
                metaData.getArgList(),
                metaData.getBody(),
                metaData.getReturnType(),
                metaData.getAnnotations(),
                metaData.getExceptions()
        );
    }

    /**
     * This method will ignore name field of metaData and use ClassName instead
     * @param metaData function metaData
     */
    public void addConstructor(FunctionMetaData metaData) {
        this.addFunction(
                metaData.getQualifier(),
                this.className,
                metaData.getArgList(),
                metaData.getBody(),
                "",
                metaData.getAnnotations(),
                metaData.getExceptions()
        );
    }

    public void addFunction(Qualifiers q,
                            String name,
                            FunctionArg[] argList,
                            String body) {
        this.addFunction(q, name, argList, body, null);
    }

    public void addFunction(Qualifiers q,
                            String name,
                            FunctionArg[] argList,
                            String body,
                            String returnType) {
        this.addFunction(q, name, argList, body, returnType, null, null);
    }

    public void addFunction(Qualifiers q,
                            String name,
                            FunctionArg[] argList,
                            String body,
                            String returnType,
                            String[] annotations,
                            String[] exceptions) {
        if (name == null || name.isEmpty()) {
            throw new RuntimeException("function name could not be null");
        }
        if (!this.classFlag) { throw new RuntimeException("your function must be created inner a class, try addClass() before this method."); }

        // return type
        if (returnType == null) {
            returnType = Identifiers.TYPE_VOID;
        }
        if (!returnType.isEmpty()) {
            returnType = returnType + Identifiers.SPACE;
        }


        StringBuilder argListBuilder = new StringBuilder();
        StringBuilder bodyBuilder = new StringBuilder();
        StringBuilder exceptionsBuilder = new StringBuilder();

        // 构建参数列表
        if (argList != null && argList.length > 0) {
            for (FunctionArg arg : argList) {
                argListBuilder.append(arg.type)
                        .append(Identifiers.SPACE)
                        .append(arg.name)
                        .append(Identifiers.COMMA)
                        .append(Identifiers.SPACE);
            }
            argListBuilder.replace(argListBuilder.length() - 2, argListBuilder.length(), "");
        }

        // 为body每一行添加制表符
        if (body != null && !body.isEmpty()) {
            String[] bodyArr = body.split(Identifiers.CRLF);
            for (String line : bodyArr) {
                bodyBuilder.append(Identifiers.TAB).append(line).append(Identifiers.CRLF);
            }
        }

        if (exceptions != null && exceptions.length > 0) {
            exceptionsBuilder.append(Identifiers.THROWS)
                    .append(Identifiers.SPACE);
            for (String exception : exceptions) {
                exceptionsBuilder.append(exception)
                        .append(Identifiers.COMMA)
                        .append(Identifiers.SPACE);
            }
            exceptionsBuilder.replace(exceptionsBuilder.length() - 2, exceptionsBuilder.length() - 1, "");
        }

        this.addAnnotations(annotations);

        this.builder.append(Identifiers.TAB)
                .append(q.getVal())
                .append(Identifiers.SPACE)
                .append(returnType)

                .append(name)

                .append(Identifiers.PARENTHESIS_L)
                .append(argListBuilder)
                .append(Identifiers.PARENTHESIS_R)
                .append(Identifiers.SPACE)

                .append(exceptionsBuilder)
                .append(Identifiers.BRACE_L)
                .append(Identifiers.CRLF)

                .append(bodyBuilder)

                .append(Identifiers.TAB)
                .append(Identifiers.BRACE_R);
        this.newLine();
    }

    private void addAnnotations(String[] annotations) {
        if (annotations != null) {
            for (String annotation : annotations) {
                this.builder.append(Identifiers.TAB)
                        .append("@")
                        .append(annotation);
                this.newLine();
            }
        }
    }

    public void addField(Qualifiers q,
                         String name,
                         String type,
                         String[] annotations) {
        this.addField(q, name, type, annotations, null);
    }

    public void addField(Qualifiers q,
                            String name,
                            String type,
                            String[] annotations,
                            String value) {
        this.addAnnotations(annotations);
        builder.append(Identifiers.TAB)
                .append(q.val)
                .append(Identifiers.SPACE)
                .append(type)
                .append(Identifiers.SPACE)
                .append(name);
        if (value != null && !value.isEmpty()) {
            builder.append(Identifiers.SPACE)
                    .append(Identifiers.EQUAL)
                    .append(Identifiers.SPACE)
                    .append(value);
        }
                builder.append(Identifiers.SEMICOLON);
        this.newLine();
    }

    private String buildImplements(String[] implementNames) {
        if (implementNames == null || implementNames.length <= 0) { return ""; }
        StringBuilder builder = new StringBuilder(Identifiers.IMPLEMENTS);
        for (String name : implementNames) {
            builder.append(Identifiers.SPACE)
                    .append(name)
                    .append(Identifiers.COMMA);
        }
        builder.replace(builder.length() - 1, builder.length(), "");
        return builder.toString();
    }

    public String finish() {
        if (this.finishFlag) { throw new RuntimeException("java text building already finished, please use text() to check"); }
        this.finishClass(); this.finishFlag = true;
        return this.builder.toString();
    }

    @Override
    public String text() {
        return this.builder.toString();
    }

    @Override
    public void writeToFile(String filename) throws IOException {
        AppUtils.writeToFile(filename, this.text());
    }

    public static void main(String[] args) {
        JavaFileBuilder builder = new JavaFileBuilder() {};
        builder.addPackage("com.magi.core");
        builder.importPackage("com.magi.core");

        builder.addClass("MyClass", new String[]{"IBaseMapper", "ITest"});

        builder.addField(Qualifiers.PUBLIC, "name", "String", null);

        builder.addFunction(Qualifiers.PUBLIC,
                "myFunc",
                new FunctionArg[]{new FunctionArg("String", "name"), new FunctionArg("String", "password")},
                "return;",
                "void",
                new String[]{"Override", "test"},
                new String[] {"IOException"}
                );
        System.out.println(builder.finish());
    }
}